﻿using System;
using System.Runtime.InteropServices;
using System.Xml.Serialization;
using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;

namespace RayDen.Library.Components {
    //[StructLayout(LayoutKind.Sequential)]
    [Serializable]
    public class Triangle {
        public static uint delta = 1;
        public uint v0;
        public uint v1;
        public uint v2;

        [NonSerialized] 
        [XmlIgnore]
        public TriangleMesh Owner;

        [NonSerialized] [XmlIgnore] 
        public AABB WorldBounds;

        [NonSerialized]
        [XmlIgnore]
        public int Index;




        [NonSerialized] 
        [XmlIgnore] 
        public Vector e1;

        [NonSerialized]
        [XmlIgnore]
        public Vector e2;


        [NonSerialized]
        [XmlIgnore]
        public bool edgesInitialized;

        public Triangle(int p, int p_2, int p_3) {
            this.v0 = (uint)p - delta;
            this.v1 = (uint)p_2 - delta;
            this.v2 = (uint)p_3 - delta;
            this.Owner = null;
            this.Index = -1;
            e1 = e2 = Vector.Zero;
            edgesInitialized = false;
        }

        public AABB WorldBound(ref Point[] vertices) {
            var pts = vertices;
            var res = new AABB(pts[this.v0], pts[this.v1]);
            res.Union(pts[v2]);
            return res;
        }

        public Point Center(ref Point[] vertices) {
            var v = WorldBound(ref vertices).Center;
            return v;
        }

        public Normal ComputeNormal(ref Vector[] vertices) {
            return (Normal)((vertices[v1] - vertices[v0]) ^ (vertices[v2] - vertices[v0]));
            //return (Normal)((vertices[v0] - vertices[v1]) ^ (vertices[v0] - vertices[v2]));
        }

        public Normal ComputeNormal(ref Point[] vertices)
        {
            return (Normal)((vertices[v1] - vertices[v0]) ^ (vertices[v2] - vertices[v0]));
            //return (Normal)((vertices[v0] - vertices[v1]) ^ (vertices[v0] - vertices[v2]));
        }

        public Normal ComputeVerticeNormal(int vInd, ref Point[] meshVertices) {
            Point v_0, v_1, v_2;

            switch (vInd) {
                case 0:
                    v_0 = meshVertices[v0];
                    v_1 = meshVertices[v1];
                    v_2 = meshVertices[v2];
                    break;
                case 1:
                    v_0 = meshVertices[v1];
                    v_1 = meshVertices[v0];
                    v_2 = meshVertices[v2];
                    break;
                case 2:
                    v_0 = meshVertices[v2];
                    v_1 = meshVertices[v1];
                    v_2 = meshVertices[v0];
                    break;
                default:
                    throw new ArgumentException();
            }
            var Normal = (Normal)((v_1 - v_0) ^ (v_2 - v_0)).Normalize();
            //var Normal = (Normal)((v_0 - v_1) ^ (v_0 - v_2)).Normalize();

            return Normal;
        }

        public float AreaV(ref Vector[] meshVertices) {
            var verts = meshVertices;
            var p0 = verts[v0];
            var p1 = verts[v1];
            var p2 = verts[v2];

            return 0.5f * ((p1 - p0) ^ (p2 - p0)).Length;
        }
        public float AreaV(ref Point[] meshVertices)
        {
            var verts = meshVertices;
            var p0 = verts[v0];
            var p1 = verts[v1];
            var p2 = verts[v2];

            return 0.5f * ((p1 - p0) ^ (p2 - p0)).Length;
        }
        public bool Intersect(ref Point[] meshVertices, ref RayInfo ray, ref float thit, ref float u, ref float v) {
            Vector s1;
            if (!edgesInitialized) {
                Point.Sub(ref meshVertices[v1], ref meshVertices[v0], out e1);
                Point.Sub(ref meshVertices[v2], ref meshVertices[v0], out e2);
                edgesInitialized = true;
            }

            Vector.Cross(ref ray.Dir, ref e2, out s1);


            var divisor = Vector.Dot(ref s1, ref e1);
            if (divisor < MathLab.Epsilon)
                return false;

            var invDivisor = 1f / divisor;


            Vector d, s2;
            d = ray.Org - meshVertices[v0];
            //Point.Sub(ref ray.Org, ref meshVertices[v0], out d);
            var b1 = Vector.Dot(ref d, ref s1) * invDivisor;
            if (b1 < 0f)
                return false;

            Vector.Cross(ref d, ref  e1, out s2);
            var b2 = Vector.Dot(ref ray.Dir, ref  s2) * invDivisor;
            if (b2 < 0f)
                return false;

            var b0 = 1f - b1 - b2;
            if (b0 < 0f)
                return false;

            var t = Vector.Dot(ref e2, ref s2) * invDivisor;
            if (t < ray.MinT || t > ray.MaxT)
                return false;

            thit = t;
            u = b1;
            v = b2;

            return true;
        }

        public void Sample(ref Point[] meshVertices, float u0, float u1, out Point samplePoint, out float b0, out float b1, out float b2) {
            b0 = 0f;
            b1 = 0f;
            MC.UniformSampleTriangle(u0, u1, ref  b0, ref b1);

            var p0 = meshVertices[v0];
            var p1 = meshVertices[v1];
            var p2 = meshVertices[v2];
            b2 = 1f - (b0) - (b1);
            samplePoint = (b0) * p0 + (b1) * p1 + (b2) * p2;
        }

        public Normal InterpolateNormal(ref Point[] meshV, float b1, float b2) {
            var n0 = this.ComputeVerticeNormal(0, ref meshV);
            var n1 = this.ComputeVerticeNormal(1, ref meshV);
            var n2 = this.ComputeVerticeNormal(2, ref meshV);
            float b0 = 1f - b1 - b2;
            //return (b0 * n0 + b2 * n1 + b1 * n2).Normalize();
            return (n0 + b1*(n2 - n1) + b2*(n2 - n0)).Normalize();
        }

        public Normal InterpolateMeshNormal(float b1, float b2) {
            return this.Owner.InterpolateTriangleNormal(this.Index, b1, b2);
        }
    }
}
